令和最新版 CoffeeScriptで書かれたnodeと上手くやっていく方法、そして決別 <わかれ> https://gyazo.com/7aa5bd48d4a0dab9fe6cb8b862460df8
moznion
https://gyazo.com/1c71fdf9357592d5d11d725512f577ad
サーバーサイドエンジニア
Seattle, Washington在住
10年モノのCoffeeScript on node
https://gyazo.com/5473c66597709a8f1fff8b56ff3c5c90
というのは若干言いすぎで、およそ9年モノです
2014年当時の状況
ES2015以前 (そりゃそう)
Babelはあった
TypeScriptブレイク以前 (だいたい2016年くらいから広くproduction readyになったという印象)
AltJSの萌芽
=> この時点でCoffeeScriptを採用したのは間違いではないはず......
2023年現在の状況
JavaScriptの言語機能がめちゃ拡張された
TypeScriptがメジャーに
DHH、TypeScriptをやめる
=> CoffeeScriptを使うメリットはおそらく失なわれたと言ってもよい
CoffeeScript現状確認
CoffeeScriptはまだ開発・メンテがactiveにされており、偉大
2017年にCoffeeScriptはv2になっている (後述)
Rails 6からCoffeeScriptは外されている
かつてCoffeeScriptが持っていた優位性はほぼJavaScript/TypeScriptにportされている
CoffeeScriptのままだと何か問題があるのか?
Syntaxが柔軟なので割と好き放題書ける
メンテコストが上がりがち
型が無い (TypeScriptの型安全性についてはここでは論じません)
周辺ツールが若干メンテされてない気配がある
CoffeeScript書ける人が少ない、そして今後 (多分) 増えない
とりあえず延命、CoffeeScript続行で騙し騙し行くぞ!!!
やっていくぞ、俺たちはそうやってきた。
CoffeeScript v2に上げる
CoffeeScript v2には様々なメリットがあります
async/awaitの導入
Arrow FunctionがNative JavaScriptにトランスパイルされる
classがNative JavaScriptにトランスパイルされる
JSXのサポート
特にasync/awaitのサポートが重要で、近年にあたってはこれが無いとコードがめちゃくちゃになる。
async.jsやbluebirdなどを使うことになり、コールバックがすごいことになる。
しかし現実は厳しい
https://gyazo.com/abcabd77150fb48ce5c08797733dd4cd
あとCoffeeScriptのasync/awaitはasync keywordを省略できてちょっと面白い。
code:async.coffee
foo = () ->
await doSomethingAsync()
これが意図通り動く
とにかく括弧を付ける
CoffeeScriptは前述の通り柔軟な記法ができる
括弧を省略すると結合がわかりにくくなるのでとにかく付けていったほうが良い
特にコールバック
そしてObject
擬似的にInterfaceを再現
JavaScriptにもinterfaceは無いだろ!! という指摘は尤もですが欲しいものは欲しい。
CoffeeScriptにもsemantics/syntax的にInterfaceは無いので擬似的なAbstract Classを作ってテストでカバー、みたいなことをしている。
code:abstract.coffee
class AbstractClass
foo: () ->
bar: () ->
code:implemented.coffee
class Implemented
foo: () ->
console.log("foo")
bar: () ->
console.log("bar")
code:test.coffee
AbstractClass = require('...')
ImplementedClass = require('...')
describe 'pseudo interface', ->
it 'should implement all abstract methods', ->
abstractMethods = Object.getOwnPropertyNames(AbstractClass.prototype)
.filter((method) -> method != 'constructor')
shouldImplementedMethods = {}
for method, _ in abstractMethods
shouldImplementedMethodsmethod = true implementedClassMethods = Object.getOwnPropertyNames(ImplementedClass)
for method, _ in implementedClassMethods
delete shouldImplementedMethodsmethod expect(shouldImplementedMethods).to.be.empty
とはいえそろそろ潮時
メンテむずい
新機能足しにくい
人がいない
decaffeinateしましょう
decaffeinateは枯れていると言っても良い
もはや2017年くらいから安定している
その上ちゃんとメンテがされていて偉大
decaffeinateした時の問題
実用上の問題は無いと思う
しかしコミットログが吹き飛ぶ
変換後のファイルすべてのコミットが Decaffeinated! とかになり歴史が分断される
CoffeeScriptの行に対応する生成後のJavaScriptについては、そのCoffeeScriptの最新のコミット情報を引きつぎたい
(コードフォーマッターを途中から導入した時などにも同じ話題がありますね)
テセウスの船 powered by git
元ファイルの行に紐付く新ファイルの行のコミットを再現して積み直すというgitのツール
DEMO
これからどうしよう
decaffeinate成功によりJavaScriptになった
変更入れたりメンテしたりがやりやすくなった
良かったですね
依然として型は無いが...
そもそもなんで今までdecaffeinateしてなかったの?
コンポーネントがデカすぎたため、適切に機能分割してサブコンポーネントに切り出される予定だった
その際に別の言語で書き直すという構想があった
それは起こらずN年の月日が流れた.....
しかし昨今、メモリ・CPUバウンドのパフォーマンスボトルネックが顕在化してきた
ランタイムをbunにする
結局Rust等で大半が書き直される予定
その他雑感
割と「CoffeeScriptの各行に行数を表わすマジックコメントを入れる」という方法でもなんとかなった
ザッと調べたりコード読んだりした感じ、decaffeinateで「元のCoffeeScriptの行がどのJavaScriptのコードを吐き出したかの対応情報」を取得する方法は無さそうだった
内部実装としても特に保持していなさそう
あれば良いのに、あるいはSource Map吐き出してくれると嬉しい気がする?
そんなものを必要としているケースが稀というのはそう
Q?